#include "stdafx.h"
#include <assert.h>

#include "nvsg/nvsg.h"
#include "nvsg/Node.h"
#include "nvsg/Triangles.h"
#include "nvsg/Transform.h"
#include "nvsg/GeoNode.h"
#include "nvsg/PointLight.h"
#include "nvsg/Material.h"
#include "nvsg/FaceAttribute.h"
#include "nvsg/StateSet.h"
#include "nvsg/PerspectiveCamera.h"
#include "nvui/GLWinRenderArea.h"
#include "nvui/TrackballCameraManipulator.h"

#include "RenderArea.h"

// enable memory leak detection
#include "nvutil/DbgNew.h"

CRenderArea::CRenderArea( ) :
	m_HWND( NULL ),
	m_HDC( NULL ),
	m_HGLRC( NULL ),
	m_SceneHandle( NULL ),
	m_RenderArea( NULL ),
	m_TrackballCameraManipulator( NULL )
{
	// one-time initialization of global data required by the NVSG API
	nvsg::nvsgInitialize( );
	
	// Create a new scene and increment the ref count, 
	// because you want to use it.
	m_SceneHandle = CreateHandle( nvsg::SceneHandle );
	m_SceneHandle->addRef( );

	nvutil::WritableObject<nvsg::Scene>( m_SceneHandle )->setRootNode( CreateSimpleTree( ) );
	CleanRenderArea( );
}

CRenderArea::~CRenderArea( )
{
	if ( m_SceneHandle )
	{
		m_SceneHandle->removeRef( );
		m_SceneHandle = NULL;
	}

	CleanRenderArea( );

	if ( m_TrackballCameraManipulator )
	{
		delete m_TrackballCameraManipulator;
		m_TrackballCameraManipulator = NULL;
	}
}

void CRenderArea::Initialize( HWND inHWND )
{
	using namespace nvmath;
	using namespace nvsg;
	using namespace nvutil;
	using namespace nvui;

	m_HWND = inHWND;
	assert( m_HWND );

	m_RenderArea = new GLWinRenderArea( );
	m_RenderArea->attachWindow( m_HWND );
	
	if ( m_RenderArea->init( ) )
	{
        // Adjust size of render area.
        m_RenderArea->setViewportSize( 800, 600 );
		m_TrackballCameraManipulator = new TrackballCameraManipulator;
		m_TrackballCameraManipulator->setRenderArea( m_RenderArea );

		// Create Camera.
		CameraHandle* theCameraHandle = CreateHandle( PerspectiveCameraHandle );
		{
			WritableObject<Camera> theCamera( theCameraHandle );

			// Make the (world space) window aspect ratio 
			// correspond to the (pixel space) viewport aspect ratio
			theCamera->setName( "ViewCamera" );
			theCamera->setAspectRatio( m_RenderArea->getAspectRatio( ) );

			// Setup the cameras' headlight
			PointLightHandle * thePointLightHandle = CreateHandle( PointLightHandle );
			{
			  WritableObject<PointLight> thePointLight( thePointLightHandle );
			  thePointLight->setAmbientColor( Vec3f( 1.f, 1.f, 1.f ) );
			  thePointLight->setDiffuseColor( Vec3f( 1.0f, 1.0f, 1.0f ) );
			  thePointLight->setSpecularColor( Vec3f( 1.0f, 1.0f, 1.0f ) );
			  thePointLight->setAttenuation( 1.0f, 0.0f, 0.0f );
			  thePointLight->setPosition( Vec3f( 5.0f, 5.0f, 0.0f ) );
			}
			theCamera->addHeadLight( thePointLightHandle );

			// Make scene fit into the viewport.
			ReadableObject<Scene> theScene( m_SceneHandle );
			theCamera->zoom( ReadableObject<Node>( theScene->getRootNode( ) )->getBoundingSphere( ), float( nvmath::PI_QUARTER ) );

			ViewStateHandle* theViewStateHandle = CreateHandle( ViewStateHandle );
			WritableObject<ViewState>( theViewStateHandle )->setCamera( theCameraHandle );

			m_RenderArea->setSceneData( m_SceneHandle, theViewStateHandle );
		}
	}
}

void CRenderArea::Render( )
{
	if ( m_RenderArea )
		m_RenderArea->renderScene( );
}

void CRenderArea::Destroy( )
{
	// free global data required by the nvsg API
	nvsg::nvsgTerminate( );
}

nvsg::NodeHandle* CRenderArea::CreateSimpleTree( ) const
{
	using namespace nvmath;
	using namespace nvsg;
	using namespace nvutil;

  // Create the root node and apply a rotation.
  // Note: 
  // We do not increment the ref count here, 
  // because we throw the root into the scene 
  // via setRoot(), and setRoot() will increment 
  // it for us.
  // setup vertices and faces  
  Vec3f vertices[8] = { Vec3f(-0.2f, -0.2f,  0.2f)
    , Vec3f(-0.2f,  0.2f,  0.2f)
    , Vec3f( 0.2f,  0.2f,  0.2f)
    , Vec3f( 0.2f, -0.2f,  0.2f)
    , Vec3f( 0.2f, -0.2f, -0.2f)
    , Vec3f( 0.2f,  0.2f, -0.2f)
    , Vec3f(-0.2f,  0.2f, -0.2f)
    , Vec3f(-0.2f, -0.2f, -0.2f) };

  Face3 faces[12]     = { {0,2,1}, {2,0,3}
  , {3,5,2}, {5,3,4}
  , {4,6,5}, {6,4,7}
  , {7,1,6}, {1,7,0}
  , {0,4,3}, {4,0,7}
  , {1,5,6}, {5,1,2} };

  // calculating face normals
  Vec3f v[36];
  Vec3f n[36];
  Face3 f[12];
  Vec3f v0, v1, v2, d0, d1, fn;
  for ( int kf=0, kv=0; kf<12; kf++, kv+=3 )
  {
    v0 = vertices[faces[kf][0]];
    v1 = vertices[faces[kf][1]];
    v2 = vertices[faces[kf][2]];

    d0 = v1 - v0;
    d1 = v2 - v0;

    fn = d0^d1;
    fn.normalize();

    f[kf][0] = kv;
    f[kf][1] = kv+1;
    f[kf][2] = kv+2;

    v[kv]    = v0;
    v[kv+1]  = v1;
    v[kv+2]  = v2;

    n[kv]    = fn; 
    n[kv+1]  = fn; 
    n[kv+2]  = fn; 
  }

  // Create a VertexAttributeSet with vertices and normals
  VertexAttributeSetHandle * vasHdl = CreateHandle(VertexAttributeSetHandle);
  {
    WritableObject<VertexAttributeSet> vas(vasHdl);
    vas->setVertices( v, 36 );
    vas->setNormals( n, 36 );
  }

  // Create a PrimitiveSet, in this case triangles.
  TrianglesHandle * shapeHdl = CreateHandle(TrianglesHandle);
  {
    WritableObject<Triangles> triangles(shapeHdl);
    triangles->setVertexAttributeSet( vasHdl );
    triangles->setFaces(f, 12);
  }

  // Create a material.
  MaterialHandle * materialHdl = CreateHandle(MaterialHandle);
  {
    WritableObject<Material> material(materialHdl);
    material->setAmbientColor( Vec3f(0.f, 0.f, 1.f) );
    material->setDiffuseColor( Vec3f(0.4f, 0.4f, 0.4f) );
    material->setEmissiveColor( Vec3f(0.f, 0.f, 0.f) );
    material->setSpecularColor( Vec3f(1.f, 1.f, 1.f) );
    material->setSpecularExponent( 10.f );
    material->setOpacity( 1.0f );
  }

  StateSetHandle * stateSetHdl = CreateHandle(StateSetHandle);
  WritableObject<StateSet>(stateSetHdl)->addAttribute(materialHdl);

  // Setup the whole scene graph
  GeoNodeHandle * geoNodeHdl = CreateHandle(GeoNodeHandle);
  WritableObject<GeoNode>(geoNodeHdl)->addGeometry(shapeHdl,stateSetHdl);

  GeoNodeHandle * geoNodeHdl2 = geoNodeHdl->clone();

  // Note: 
  // AddChild() will increment the reference count 
  // of the added node.
  Trafo shapeTrafo2;
  shapeTrafo2.setOrientation( Quatf(Vec3f(0.0f, 1.0f, 0.0f ), float(PI_QUARTER)) );
  TransformHandle * pShapeTrafo2 = CreateHandle(TransformHandle);
  {
    WritableObject<Transform> wot(pShapeTrafo2);
    wot->setTrafo( shapeTrafo2 );
    wot->addChild( geoNodeHdl );
  }

  Trafo shapeTrafo;
  shapeTrafo.setOrientation( Quatf(Vec3f(1.0f, 0.0f, 0.0f ), float(PI_QUARTER)) );
  TransformHandle * pShapeTrafo = CreateHandle(TransformHandle);
  {
    WritableObject<Transform> wot(pShapeTrafo);
    wot->setTrafo(shapeTrafo);
    wot->addChild( pShapeTrafo2 );
  }

  Trafo shape2Trafo2;
  shape2Trafo2.setOrientation( Quatf(Vec3f(0.0f, 1.0f, 0.0f ), float(PI_QUARTER)) );
  TransformHandle * pShape2Trafo2 = CreateHandle(TransformHandle);
  {
    WritableObject<Transform> wot(pShape2Trafo2);
    wot->setTrafo(shape2Trafo2);
    wot->addChild(geoNodeHdl2);
  }

  Trafo shape2Trafo;
  shape2Trafo.setOrientation( Quatf(Vec3f(1.0f, 0.0f, 0.0f ), 0.0f) );
  shape2Trafo.setTranslation( Vec3f( -1.0f, 0.0f, 0.0f ) ); 
  TransformHandle * pShape2Trafo = CreateHandle(TransformHandle);
  {
    WritableObject<Transform> wot(pShape2Trafo);
    wot->setTrafo(shape2Trafo);
    wot->addChild(pShape2Trafo2);
  }

  Trafo shape3Trafo;
  shape3Trafo.setOrientation( Quatf(Vec3f(1.0f, 0.0f, 0.0f ), 0.0f) );
  shape3Trafo.setTranslation( Vec3f( 1.0f, 0.0f, 0.0f ) ); 
  TransformHandle * pShape3Trafo = CreateHandle(TransformHandle);
  {
    WritableObject<Transform> wot(pShape3Trafo);
    wot->setTrafo(shape3Trafo);
    wot->addChild( geoNodeHdl2 );
  }

  GroupHandle * rootHdl = CreateHandle(GroupHandle);
  {
    WritableObject<Group> root(rootHdl);
    root->addChild( pShapeTrafo );
    root->addChild( pShape2Trafo );
    root->addChild( pShape3Trafo );
  }

  return( rootHdl );
}

void CRenderArea::CleanRenderArea( )
{
	// Render area clean up.
	if ( m_RenderArea ) 
	{
		m_RenderArea->destroy( );
		delete m_RenderArea;
		m_RenderArea = NULL;
	}
}